/**
 * See [openstf/minitouch](https://github.com/openstf/minitouch)
 */

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <math.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/un.h>
#include <unistd.h>

#include "minitouch.h"
#include "libevdev.h"

#define MAX_SUPPORTED_CONTACTS  10
#define VERSION                 1
#define DEFAULT_SOCKET_NAME     "minitouch"

typedef struct
{
  int enabled;
  int tracking_id;
  int x;
  int y;
  int pressure;
} contact_t;

typedef struct
{
  int fd;
  int score;
  char path[100];

  // struct libevdev* evdev;
  std::shared_ptr<anbox::input::Device> device;
  std::vector<anbox::input::Event>      events;

  int has_mtslot;       // TODO: to remove xiaofeng 2019-01-12
  int has_tracking_id;
  int has_key_btn_touch;
  int has_touch_major;
  int has_width_major;
  int has_pressure;
  int min_pressure;
  int max_pressure;
  int is_singletouch;
  int max_x;
  int max_y;
  int max_contacts;
  int max_tracking_id;
  int tracking_id;
  contact_t contacts[MAX_SUPPORTED_CONTACTS];
  int active_contacts;
} internal_state_t;

#define WRITE_EVENT(state, type, code, value) _write_event(state, type, #type, code, #code, value)

static int _write_event(internal_state_t* state,
    uint16_t type, const char* type_name,
    uint16_t code, const char* code_name,
    int32_t value)
{
    // It seems that most devices do not require the event timestamps at all.
    // Left here for reference should such a situation arise.
    //
    //   timespec ts;
    //   clock_gettime(CLOCK_MONOTONIC, &ts);
    //   input_event event = {{ts.tv_sec, ts.tv_nsec / 1000}, type, code, value};

    (void)type_name;
    (void)code_name;

    state->events.push_back({ type, code, value });

    // DEBUG("Event: %-12s %-20s %08x\n", type_name, code_name, value);

    if (type == EV_SYN && code == SYN_REPORT) {
        state->device->send_events(state->events);
        state->events.clear();
    }

    return 0;
}

static int next_tracking_id(internal_state_t* state)
{
  if (state->tracking_id < INT_MAX)
  {
    state->tracking_id += 1;
  }
  else
  {
    state->tracking_id = 0;
  }

  return state->tracking_id;
}

static int type_a_multi_touch_commit(internal_state_t* state)
{
  int contact;
  int found_any = 0;

  for (contact = 0; contact < state->max_contacts; ++contact)
  {
    switch (state->contacts[contact].enabled)
    {
      case 1: // WENT_DOWN
        found_any = 1;

        state->active_contacts += 1;

        if (state->has_tracking_id)
          WRITE_EVENT(state, EV_ABS, ABS_MT_TRACKING_ID, contact);

        // Send BTN_TOUCH on first contact only.
        if (state->active_contacts == 1 && state->has_key_btn_touch)
          WRITE_EVENT(state, EV_KEY, BTN_TOUCH, 1);

        if (state->has_touch_major)
          WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MAJOR, 0x00000006);

        if (state->has_width_major)
          WRITE_EVENT(state, EV_ABS, ABS_MT_WIDTH_MAJOR, 0x00000004);

        if (state->has_pressure)
          WRITE_EVENT(state, EV_ABS, ABS_MT_PRESSURE, state->contacts[contact].pressure);

        WRITE_EVENT(state, EV_ABS, ABS_MT_POSITION_X, state->contacts[contact].x);
        WRITE_EVENT(state, EV_ABS, ABS_MT_POSITION_Y, state->contacts[contact].y);

        WRITE_EVENT(state, EV_SYN, SYN_MT_REPORT, 0);

        state->contacts[contact].enabled = 2;
        break;
      case 2: // MOVED
        found_any = 1;

        if (state->has_tracking_id)
          WRITE_EVENT(state, EV_ABS, ABS_MT_TRACKING_ID, contact);

        if (state->has_touch_major)
          WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MAJOR, 0x00000006);

        if (state->has_width_major)
          WRITE_EVENT(state, EV_ABS, ABS_MT_WIDTH_MAJOR, 0x00000004);

        if (state->has_pressure)
          WRITE_EVENT(state, EV_ABS, ABS_MT_PRESSURE, state->contacts[contact].pressure);

        WRITE_EVENT(state, EV_ABS, ABS_MT_POSITION_X, state->contacts[contact].x);
        WRITE_EVENT(state, EV_ABS, ABS_MT_POSITION_Y, state->contacts[contact].y);

        WRITE_EVENT(state, EV_SYN, SYN_MT_REPORT, 0);
        break;
      case 3: // WENT_UP
        found_any = 1;

        state->active_contacts -= 1;

        if (state->has_tracking_id)
          WRITE_EVENT(state, EV_ABS, ABS_MT_TRACKING_ID, contact);

        // Send BTN_TOUCH only when no contacts remain.
        if (state->active_contacts == 0 && state->has_key_btn_touch)
          WRITE_EVENT(state, EV_KEY, BTN_TOUCH, 0);

        WRITE_EVENT(state, EV_SYN, SYN_MT_REPORT, 0);

        state->contacts[contact].enabled = 0;
        break;
    default:
        assert(0);
        break;
    }
  }

  if (found_any)
    WRITE_EVENT(state, EV_SYN, SYN_REPORT, 0);

  return 1;
}

static int type_a_single_touch_commit(internal_state_t* state)
{
  int contact;
  int found_any = 0;
  for (contact = 0; contact < state->max_contacts; ++contact)
  {
    switch (state->contacts[contact].enabled)
    {
      case 1: // WENT_DOWN
        found_any = 1;
        if (state->has_tracking_id)
          WRITE_EVENT(state, EV_ABS, ABS_MT_TRACKING_ID, contact);
        if (state->has_key_btn_touch)
          WRITE_EVENT(state, EV_KEY, BTN_TOUCH, 1);
        if (state->has_touch_major)
          WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MAJOR, 0x00000006);
        if (state->has_width_major)
          WRITE_EVENT(state, EV_ABS, ABS_MT_WIDTH_MAJOR, 0x00000004);
        if (state->has_pressure)
          WRITE_EVENT(state, EV_ABS, ABS_MT_PRESSURE, state->contacts[contact].pressure);
        WRITE_EVENT(state, EV_ABS, ABS_X, state->contacts[contact].x);
        WRITE_EVENT(state, EV_ABS, ABS_Y, state->contacts[contact].y);
        state->contacts[contact].enabled = 2;
        break;
      case 2: // MOVED
        found_any = 1;
        if (state->has_tracking_id)
          WRITE_EVENT(state, EV_ABS, ABS_MT_TRACKING_ID, contact);
        if (state->has_touch_major)
          WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MAJOR, 0x00000006);
        if (state->has_width_major)
          WRITE_EVENT(state, EV_ABS, ABS_MT_WIDTH_MAJOR, 0x00000004);
        if (state->has_pressure)
          WRITE_EVENT(state, EV_ABS, ABS_MT_PRESSURE, state->contacts[contact].pressure);
        WRITE_EVENT(state, EV_ABS, ABS_X, state->contacts[contact].x);
        WRITE_EVENT(state, EV_ABS, ABS_Y, state->contacts[contact].y);
        break;
      case 3: // WENT_UP
        found_any = 1;
        if (state->has_tracking_id)
          WRITE_EVENT(state, EV_ABS, ABS_MT_TRACKING_ID, contact);
        if (state->has_key_btn_touch)
          WRITE_EVENT(state, EV_KEY, BTN_TOUCH, 0);
        WRITE_EVENT(state, EV_SYN, SYN_MT_REPORT, 0);
        state->contacts[contact].enabled = 0;
        break;
    default:
        assert(0);
        break;
    }
  }
  if (found_any)
    WRITE_EVENT(state, EV_SYN, SYN_REPORT, 0);
  return 1;
}

static int type_a_touch_panic_reset_all(internal_state_t* state)
{
  int contact;

  for (contact = 0; contact < state->max_contacts; ++contact)
  {
    switch (state->contacts[contact].enabled)
    {
      case 1: // WENT_DOWN
      case 2: // MOVED
        // Force everything to WENT_UP
        state->contacts[contact].enabled = 3;
        break;
    default:
        assert(0);
        break;
    }
  }

  // return type_a_commit(state);
  if (state->is_singletouch)
    return type_a_single_touch_commit(state);
  else
    return type_a_multi_touch_commit(state);
}

static int type_a_touch_down(internal_state_t* state, int contact, int x, int y, int pressure)
{
  if (contact >= state->max_contacts)
  {
    return 0;
  }

  if (state->contacts[contact].enabled)
  {
    type_a_touch_panic_reset_all(state);
  }

  state->contacts[contact].enabled = 1;
  state->contacts[contact].x = x;
  state->contacts[contact].y = y;
  state->contacts[contact].pressure = pressure;

  return 1;
}

static int type_a_touch_move(internal_state_t* state, int contact, int x, int y, int pressure)
{
  if (contact >= state->max_contacts || !state->contacts[contact].enabled)
  {
    return 0;
  }

  state->contacts[contact].enabled = 2;
  state->contacts[contact].x = x;
  state->contacts[contact].y = y;
  state->contacts[contact].pressure = pressure;

  return 1;
}

static int type_a_touch_up(internal_state_t* state, int contact)
{
  if (contact >= state->max_contacts || !state->contacts[contact].enabled)
  {
    return 0;
  }

  state->contacts[contact].enabled = 3;

  return 1;
}

static int type_b_commit(internal_state_t* state)
{
  WRITE_EVENT(state, EV_SYN, SYN_REPORT, 0);

  return 1;
}

static int type_b_touch_panic_reset_all(internal_state_t* state)
{
  int contact;
  int found_any = 0;

  for (contact = 0; contact < state->max_contacts; ++contact)
  {
    if (state->contacts[contact].enabled)
    {
      state->contacts[contact].enabled = 0;
      found_any = 1;
    }
  }

  return found_any ? type_b_commit(state) : 1;
}

static int type_b_multi_touch_down(internal_state_t* state, int contact, int x, int y, int pressure)
{
  if (contact >= state->max_contacts)
  {
    return 0;
  }

  if (state->contacts[contact].enabled)
  {
    type_b_touch_panic_reset_all(state);
  }

  state->contacts[contact].enabled = 1;
  state->contacts[contact].tracking_id = next_tracking_id(state);
  state->active_contacts += 1;

  WRITE_EVENT(state, EV_ABS, ABS_MT_TRACKING_ID, state->contacts[contact].tracking_id);
  WRITE_EVENT(state, EV_ABS, ABS_MT_SLOT, contact);

  // Send BTN_TOUCH on first contact only.
  if (state->active_contacts == 1 && state->has_key_btn_touch)
    WRITE_EVENT(state, EV_KEY, BTN_TOUCH, 1);

  WRITE_EVENT(state, EV_KEY, BTN_TOOL_FINGER, 1);
  WRITE_EVENT(state, EV_ABS, ABS_MT_POSITION_X, x);
  WRITE_EVENT(state, EV_ABS, ABS_MT_POSITION_Y, y);

  if (state->has_touch_major) {
    WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MAJOR, 24);
    WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MINOR, 24);
  }

  if (state->has_width_major)
    WRITE_EVENT(state, EV_ABS, ABS_MT_WIDTH_MAJOR, 0x00000004);

  if (state->has_pressure)
    WRITE_EVENT(state, EV_ABS, ABS_MT_PRESSURE, pressure);

  return 1;
}

static int type_b_single_touch_down(internal_state_t* state, int contact, int x, int y, int pressure)
{
  if (contact >= state->max_contacts)
  {
    return 0;
  }
  if (state->contacts[contact].enabled)
  {
    type_b_touch_panic_reset_all(state);
  }

  state->contacts[contact].enabled = 1;
  state->contacts[contact].tracking_id = next_tracking_id(state);

  WRITE_EVENT(state, EV_ABS, ABS_MT_TRACKING_ID,
              state->contacts[contact].tracking_id);
  WRITE_EVENT(state, EV_ABS, ABS_MT_SLOT, contact);
  
  if (state->has_key_btn_touch)
    WRITE_EVENT(state, EV_KEY, BTN_TOUCH, 1);
  
  WRITE_EVENT(state, EV_ABS, ABS_X, x);
  WRITE_EVENT(state, EV_ABS, ABS_Y, y);

  if (state->has_touch_major) {
    WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MAJOR, 24);
    WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MINOR, 24);
  }
  
  if (state->has_width_major)
    WRITE_EVENT(state, EV_ABS, ABS_MT_WIDTH_MAJOR, 0x00000004);
  
  if (state->has_pressure)
    WRITE_EVENT(state, EV_ABS, ABS_MT_PRESSURE, pressure);
  
  return 1;
}

static int type_b_multi_touch_move(internal_state_t* state, int contact, int x, int y, int pressure)
{
  if (contact >= state->max_contacts || !state->contacts[contact].enabled)
  {
    return 0;
  }

  WRITE_EVENT(state, EV_ABS, ABS_MT_SLOT, contact);

  WRITE_EVENT(state, EV_ABS, ABS_MT_POSITION_X, x);
  WRITE_EVENT(state, EV_ABS, ABS_MT_POSITION_Y, y);

  if (state->has_touch_major) {
    WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MAJOR, 24);
    WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MINOR, 24);
  }

  if (state->has_width_major)
    WRITE_EVENT(state, EV_ABS, ABS_MT_WIDTH_MAJOR, 0x00000004);

  if (state->has_pressure)
    WRITE_EVENT(state, EV_ABS, ABS_MT_PRESSURE, pressure);

  return 1;
}

static int type_b_single_touch_move(internal_state_t* state, int contact, int x, int y, int pressure)
{
  if (contact >= state->max_contacts || !state->contacts[contact].enabled)
  {
    return 0;
  }
  
  WRITE_EVENT(state, EV_ABS, ABS_MT_SLOT, contact);

  WRITE_EVENT(state, EV_ABS, ABS_X, x);
  WRITE_EVENT(state, EV_ABS, ABS_Y, y);

  if (state->has_touch_major) {
    WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MAJOR, 24);
    WRITE_EVENT(state, EV_ABS, ABS_MT_TOUCH_MINOR, 24);
  }
  
  if (state->has_width_major)
    WRITE_EVENT(state, EV_ABS, ABS_MT_WIDTH_MAJOR, 0x00000004);
  
  if (state->has_pressure)
    WRITE_EVENT(state, EV_ABS, ABS_MT_PRESSURE, pressure);
  
  return 1;
}

static int type_b_touch_up(internal_state_t* state, int contact)
{
  if (contact >= state->max_contacts || !state->contacts[contact].enabled)
  {
    return 0;
  }

  state->contacts[contact].enabled = 0;
  state->contacts[contact].enabled = 0;
  state->active_contacts -= 1;

  WRITE_EVENT(state, EV_ABS, ABS_MT_TRACKING_ID, -1);
  WRITE_EVENT(state, EV_ABS, ABS_MT_SLOT, contact);

  // Send BTN_TOUCH only when no contacts remain.
  if (state->active_contacts == 0 && state->has_key_btn_touch)
    WRITE_EVENT(state, EV_KEY, BTN_TOUCH, 0);

  WRITE_EVENT(state, EV_KEY, BTN_TOOL_FINGER, 0);

  return 1;
}

static int touch_down(internal_state_t* state, int contact, int x, int y, int pressure)
{
  if (state->has_mtslot)
  {
    // return type_b_touch_down(state, contact, x, y, pressure);
    if (state->is_singletouch)
      return type_b_single_touch_down(state, contact, x, y, pressure);
    else
      return type_b_multi_touch_down(state, contact, x, y, pressure);
  }
  else
  {
    return type_a_touch_down(state, contact, x, y, pressure);
  }
}

static int touch_move(internal_state_t* state, int contact, int x, int y, int pressure)
{
  if (state->has_mtslot)
  {
    // return type_b_touch_move(state, contact, x, y, pressure);
    if (state ->is_singletouch)
      return type_b_single_touch_move(state, contact, x, y, pressure);
    else
      return type_b_multi_touch_move(state, contact, x, y, pressure);
  }
  else
  {
    return type_a_touch_move(state, contact, x, y, pressure);
  }
}

static int touch_up(internal_state_t* state, int contact)
{
  if (state->has_mtslot)
  {
    return type_b_touch_up(state, contact);
  }
  else
  {
    return type_a_touch_up(state, contact);
  }
}

static int touch_panic_reset_all(internal_state_t* state)
{
  if (state->has_mtslot)
  {
    return type_b_touch_panic_reset_all(state);
  }
  else
  {
    return type_a_touch_panic_reset_all(state);
  }
}

static int commit(internal_state_t* state)
{
  if (state->has_mtslot)
  {
    return type_b_commit(state);
  }
  else
  {
    // return type_a_commit(state);
    if (state->is_singletouch)
      return type_a_single_touch_commit(state);
    else
      return type_a_multi_touch_commit(state);
  }
}

static void parse_input(const char* buffer, internal_state_t* state)
{
  char* cursor;
  long int contact, x, y, pressure, wait;

  cursor = const_cast<char *>(buffer);
  cursor += 1;

  switch (buffer[0])
  {
    case 'c': // COMMIT
      commit(state);
      break;
    case 'r': // RESET
      touch_panic_reset_all(state);
      break;
    case 'd': // TOUCH DOWN
      contact = strtol(cursor, &cursor, 10);
      x = strtol(cursor, &cursor, 10);
      y = strtol(cursor, &cursor, 10);
      pressure = strtol(cursor, &cursor, 10);
      touch_down(state, contact, x, y, pressure);
      break;
    case 'm': // TOUCH MOVE
      contact = strtol(cursor, &cursor, 10);
      x = strtol(cursor, &cursor, 10);
      y = strtol(cursor, &cursor, 10);
      pressure = strtol(cursor, &cursor, 10);
      touch_move(state, contact, x, y, pressure);
      break;
    case 'u': // TOUCH UP
      contact = strtol(cursor, &cursor, 10);
      touch_up(state, contact);
      break;
    case 'w':
      wait = strtol(cursor, &cursor, 10);
      // DEBUG("minitouch: Waiting %ld ms\n", wait);
      usleep(wait * 1000);
      break;
    default:
      break;
  }
}

#include "anbox/logger.h"
#include "anbox/network/delegate_connection_creator.h"
#include "anbox/network/delegate_message_processor.h"
#include "anbox/network/local_socket_messenger.h"
#include "anbox/system_configuration.h"
#include <boost/format.hpp>

namespace anbox {
namespace input {

struct DeviceState {
    internal_state_t state;
};

std::string DeviceMonitor::socket_file()
{
    /*
     * default sockpath in host is '~/.anbox/common-55??/runtime/anbox/sockets/minitouch'
     * we need reverse it into container via:
     *   `adb reverse localabstract:minitouch localabstrat:$sockpath`
     * or using lxc device mount (as minitouch-agent):
     *    lxc.mount.entry $sockpath /dev/minitouch
     * 
     * in container: `sockpath = DEFAULT_SOCKET_NAME`
     */
    std::string sockpath = (boost::format("%1%/%2%") %
        SystemConfiguration::instance().socket_dir() % DEFAULT_SOCKET_NAME).str();
    return sockpath;
}

std::shared_ptr<DeviceMonitor>
DeviceMonitor::create(const std::shared_ptr<Device> &device, 
                      const std::shared_ptr<Runtime> &runtime) {
    auto sp = std::make_shared<DeviceMonitor>(device);

    auto delegate_connector = std::make_shared< network::DelegateConnectionCreator<boost::asio::local::stream_protocol> >(
        [sp](std::shared_ptr<boost::asio::local::stream_protocol::socket> const &socket) { 
            sp->new_client(socket);
        }
    );

    const std::string &sockpath = socket_file();
    INFO("DeviceMonitor: intializing '%s'", sockpath.c_str());

    sp->connector_ = std::make_shared<network::PublishedSocketConnector>(
        sockpath, runtime, delegate_connector);

    // FIXME: currently creating the socket creates it with the rights of
    // the user we're running as. As this one is mapped into the container
    ::chmod(sockpath.c_str(), 0777);

  return sp;
}

DeviceMonitor::DeviceMonitor(const std::shared_ptr<Device> &device)
  : device_(device), 
    state_(new DeviceState()),
    next_connection_id_(0),
    connections_(std::make_shared<network::Connections<network::SocketConnection>>())
{
    /*
     * see minitouch::consider_device()
     */
    internal_state_t *state = &(state_->state);
    int score = 1000;

    // libevdev_has_event_code(evdev, EV_ABS, ABS_MT_TOOL_TYPE)
    // score -= tool_max - MT_TOOL_FINGER;

    // libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT)
    score += 1000;

    // libevdev_has_property(evdev, INPUT_PROP_DIRECT)
    score += 10000;

    state->fd     = -1;
    state->score  = score;
    state->device = device;
    strncpy(state->path, device->socket_path().data(), sizeof(state->path) - 1);
}

DeviceMonitor::~DeviceMonitor() {
    // NOTHING
}

void 
DeviceMonitor::new_client(std::shared_ptr<boost::asio::local::stream_protocol::socket> const &socket) {
    INFO("client #%d up", next_connection_id_);

    auto const messenger =
        std::make_shared<network::LocalSocketMessenger>(socket);
    auto const &connection = std::make_shared<network::SocketConnection>(
        messenger, messenger, next_id(), connections_,
        std::make_shared<InputMessageProcessor>(this)
    );

    sync_abs_configuration();

    connection->set_name("minitouch");
    connections_->add(connection);

    /* 
     * see minitouch::io_handler()
     */
    internal_state_t *state = &(state_->state);
    char buffer[256];
    char *pbuf = &buffer[0];
    char *pend = &buffer[sizeof(buffer)];
    size_t len;

    // Tell version
    len = snprintf(pbuf, pend - pbuf, "v %d\n", VERSION);
    connection->send(pbuf, len);

    // Tell limits
    len = snprintf(pbuf, pend - pbuf, "^ %d %d %d %d\n",
                   state->max_contacts, state->max_x, state->max_y, 
                   state->max_pressure);
    connection->send(pbuf, len);

    // Tell pid : set in minitouch.c:proxy_handler()
    // len = snprintf(pbuf, pend - pbuf, "$ %d\n", getpid());
    // connection->send(pbuf, len);
    // INFO("<<< minitouch:\n%s<<<", buffer);

    // reading inputs
    connection->read_next_message();
}

/**
 * see minitouch::set_abs_configuration
 */
void DeviceMonitor::sync_abs_configuration()
{
    int i;

    internal_state_t *state = &(state_->state);

    state->has_mtslot        = 1;
    state->has_tracking_id   = 0;
    state->has_key_btn_touch = 1;
    state->has_touch_major   = 1;
    state->has_width_major   = 0;
    state->is_singletouch    = 0;

    state->has_pressure      = 1;
    state->min_pressure      = 0;
    state->max_pressure      = 255;

    state->max_x = device_->info_.abs_max[ABS_MT_POSITION_X];
    state->max_y = device_->info_.abs_max[ABS_MT_POSITION_Y];

    state->max_tracking_id = INT_MAX;
    state->tracking_id = 0;

    // state->max_contacts = MAX_SUPPORTED_CONTACTS;
    state->max_contacts = 1;

    for (i = 0; i < state->max_contacts; ++i) {
        state->contacts[i].enabled = 0;
    }
}

bool DeviceMonitor::parse_input(const std::vector<std::uint8_t> &data) {
    std::string str(reinterpret_cast<const char *>(data.data()), data.size());
    auto lines = anbox::utils::string_split(str, '\n');

    for (auto line: lines) {
        if (line.size() > 0) {
            //DEBUG("%s got message: %s", 
            //      DEFAULT_SOCKET_NAME /* device_->socket_path().c_str() */, line.data());
            ::parse_input(line.data(), &(state_->state));
        }
    }

    return true;
}


} // namespace input
} // namespace anbox
